#include <stdio.h>
#include <string.h>
#include <vector>
#include <string>
#include <error.cpp>
#include <stdlib.h>
#include <openssl/ec.h>
#include <openssl/sm2.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/engine.h>
#include <openssl/objects.h>
#include <openssl/opensslconf.h>
using namespace std;

extern long _BIO_get_mem_data(BIO *bio, char **pp);
EVP_PKEY_CTX *new_pkey_keygen_ctx(const char *alg, ENGINE *e) {
	EVP_PKEY_CTX *ret = NULL;
	EVP_PKEY_CTX *ctx = NULL;
	const EVP_PKEY_ASN1_METHOD *ameth;
	ENGINE *eng = NULL;
	int pkey_id;
	if (!(ameth = EVP_PKEY_asn1_find_str(&eng, alg, -1))) {
		return NULL;
	}
	EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);
	ENGINE_finish(eng);
	if (!(ctx = EVP_PKEY_CTX_new_id(pkey_id, e))) {
		goto end;
	}
	ret = ctx;
	ctx = NULL;
end:
	EVP_PKEY_CTX_free(ctx);
	return ret;
}
EVP_PKEY *pem_read_bio_pubkey(BIO *bio) {
	return PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
}
int pem_write_bio_pubkey(BIO *bio, EVP_PKEY *pkey) {
	return PEM_write_bio_PUBKEY(bio, pkey);
}
int pem_write_bio_privatekey(BIO *bio, EVP_PKEY *pkey,
	const EVP_CIPHER *cipher, const char *pass) {
	return PEM_write_bio_PrivateKey(bio, pkey, cipher, NULL, 0, NULL, (void *)pass);
}
int sign_nids[] = {
#ifndef OPENSSL_NO_SM2
	NID_sm2sign,
#endif
	NID_ecdsa_with_Recommended,
#ifndef OPENSSL_NO_SHA
	NID_ecdsa_with_SHA1,
	NID_ecdsa_with_SHA256,
	NID_ecdsa_with_SHA512,
# ifndef OPENSSL_NO_RSA
	NID_sha1WithRSAEncryption,
	NID_sha256WithRSAEncryption,
	NID_sha512WithRSAEncryption,
# endif
# ifndef OPENSSL_NO_DSA
	NID_dsaWithSHA1,
# endif
#endif
};
static int get_sign_info(const char *alg, int *ppkey_type,
	const EVP_MD **pmd, int *pec_scheme)
{
	int pkey_type;
	const EVP_MD *md = NULL;
	int ec_scheme = -1;
	switch (OBJ_txt2nid(alg)) {
	case NID_sm2sign:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		break;
	case NID_ecdsa_with_Recommended:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		break;
	case NID_ecdsa_with_SHA1:
		pkey_type = EVP_PKEY_EC;
		md = EVP_sha1();
		ec_scheme = NID_secg_scheme;
		break;
	case NID_ecdsa_with_SHA256:
		pkey_type = EVP_PKEY_EC;
		md = EVP_sha256();
		ec_scheme = NID_secg_scheme;
		break;
	case NID_ecdsa_with_SHA512:
		pkey_type = EVP_PKEY_EC;
		md = EVP_sha512();
		ec_scheme = NID_secg_scheme;
		break;
	case NID_sha1WithRSAEncryption:
		pkey_type = EVP_PKEY_RSA;
		md = EVP_sha1();
		break;
	case NID_sha256WithRSAEncryption:
		pkey_type = EVP_PKEY_RSA;
		md = EVP_sha256();
		break;
	case NID_sha512WithRSAEncryption:
		pkey_type = EVP_PKEY_RSA;
		md = EVP_sha512();
		break;
	case NID_dsaWithSHA1:
		pkey_type = EVP_PKEY_DSA;
		md = EVP_sha1();
		break;
	default:
		return 0;
	}
	*ppkey_type = pkey_type;
	*pmd = md;
	*pec_scheme = ec_scheme;
	return 1;
}
int pke_nids[] = {
#ifndef OPENSSL_NO_RSA
	NID_rsaesOaep,
#endif
#ifndef OPENSSL_NO_ECIES
	NID_ecies_recommendedParameters,
	NID_ecies_specifiedParameters,
# ifndef OPENSSL_NO_SHA
	NID_ecies_with_x9_63_sha1_xor_hmac,
	NID_ecies_with_x9_63_sha256_xor_hmac,
	NID_ecies_with_x9_63_sha512_xor_hmac,
	NID_ecies_with_x9_63_sha1_aes128_cbc_hmac,
	NID_ecies_with_x9_63_sha256_aes128_cbc_hmac,
	NID_ecies_with_x9_63_sha512_aes256_cbc_hmac,
	NID_ecies_with_x9_63_sha256_aes128_ctr_hmac,
	NID_ecies_with_x9_63_sha512_aes256_ctr_hmac,
	NID_ecies_with_x9_63_sha256_aes128_cbc_hmac_half,
	NID_ecies_with_x9_63_sha512_aes256_cbc_hmac_half,
	NID_ecies_with_x9_63_sha256_aes128_ctr_hmac_half,
	NID_ecies_with_x9_63_sha512_aes256_ctr_hmac_half,
	NID_ecies_with_x9_63_sha1_aes128_cbc_cmac,
	NID_ecies_with_x9_63_sha256_aes128_cbc_cmac,
	NID_ecies_with_x9_63_sha512_aes256_cbc_cmac,
	NID_ecies_with_x9_63_sha256_aes128_ctr_cmac,
	NID_ecies_with_x9_63_sha512_aes256_ctr_cmac,
# endif
#endif
#ifndef OPENSSL_NO_SM2
	NID_sm2encrypt_with_sm3,
# ifndef OPENSSL_NO_SHA
	NID_sm2encrypt_with_sha1,
	NID_sm2encrypt_with_sha256,
	NID_sm2encrypt_with_sha512,
# endif
#endif
};
static int get_pke_info(const char *alg, int *ppkey_type,
	int *pec_scheme, int *pec_encrypt_param)
{
	int pkey_type = 0;
	int ec_scheme = 0;
	int ec_encrypt_param = 0;
	switch (OBJ_txt2nid(alg)) {
	case NID_rsaesOaep:
		pkey_type = EVP_PKEY_RSA;
		break;
	case NID_ecies_recommendedParameters:
	case NID_ecies_specifiedParameters:
	case NID_ecies_with_x9_63_sha1_xor_hmac:
	case NID_ecies_with_x9_63_sha256_xor_hmac:
	case NID_ecies_with_x9_63_sha512_xor_hmac:
	case NID_ecies_with_x9_63_sha1_aes128_cbc_hmac:
	case NID_ecies_with_x9_63_sha256_aes128_cbc_hmac:
	case NID_ecies_with_x9_63_sha512_aes256_cbc_hmac:
	case NID_ecies_with_x9_63_sha256_aes128_ctr_hmac:
	case NID_ecies_with_x9_63_sha512_aes256_ctr_hmac:
	case NID_ecies_with_x9_63_sha256_aes128_cbc_hmac_half:
	case NID_ecies_with_x9_63_sha512_aes256_cbc_hmac_half:
	case NID_ecies_with_x9_63_sha256_aes128_ctr_hmac_half:
	case NID_ecies_with_x9_63_sha512_aes256_ctr_hmac_half:
	case NID_ecies_with_x9_63_sha1_aes128_cbc_cmac:
	case NID_ecies_with_x9_63_sha256_aes128_cbc_cmac:
	case NID_ecies_with_x9_63_sha512_aes256_cbc_cmac:
	case NID_ecies_with_x9_63_sha256_aes128_ctr_cmac:
	case NID_ecies_with_x9_63_sha512_aes256_ctr_cmac:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ec_encrypt_param = OBJ_txt2nid(alg);
		break;
	case NID_sm2encrypt_with_sm3:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sm3;
		break;
	case NID_sm2encrypt_with_sha1:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sha1;
		break;
	case NID_sm2encrypt_with_sha256:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sha256;
		break;
	case NID_sm2encrypt_with_sha512:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ec_encrypt_param = NID_sha512;
		break;
	default:
		return 0;
	}
	*ppkey_type = pkey_type;
	*pec_scheme = ec_scheme;
	*pec_encrypt_param = ec_encrypt_param;
	return 1;
}
int exch_nids[] = {
#ifndef OPENSSL_NO_SM2
	NID_sm2exchange,
#endif
#ifndef OPENSSL_NO_SHA
	NID_dhSinglePass_stdDH_sha1kdf_scheme,
	NID_dhSinglePass_stdDH_sha224kdf_scheme,
	NID_dhSinglePass_stdDH_sha256kdf_scheme,
	NID_dhSinglePass_stdDH_sha384kdf_scheme,
	NID_dhSinglePass_stdDH_sha512kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha1kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha224kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha256kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha384kdf_scheme,
	NID_dhSinglePass_cofactorDH_sha512kdf_scheme,
#endif
#ifndef OPENSSL_NO_DH
	NID_dhKeyAgreement,
#endif
};
static int get_exch_info(const char *alg, int *ppkey_type, int *pec_scheme,
	int *pecdh_cofactor_mode, int *pecdh_kdf_type, int *pecdh_kdf_md,
	int *pecdh_kdf_outlen, char **pecdh_kdf_ukm, int *pecdh_kdf_ukmlen)
{
	int pkey_type = 0;
	int ec_scheme = 0;
	int ecdh_cofactor_mode = 0;
	int ecdh_kdf_type = 0;
	int ecdh_kdf_md = 0;
	int ecdh_kdf_outlen = 0;
	char *ecdh_kdf_ukm = NULL;
	int ecdh_kdf_ukmlen = 0;
	switch (OBJ_txt2nid(alg)) {
	case NID_sm2exchange:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_sm_scheme;
		ecdh_kdf_md = NID_sm3;
		break;
	case NID_dhSinglePass_stdDH_sha1kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha1;
		break;
	case NID_dhSinglePass_stdDH_sha224kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha224;
		break;
	case NID_dhSinglePass_stdDH_sha256kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha256;
		break;
	case NID_dhSinglePass_stdDH_sha384kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha384;
		break;
	case NID_dhSinglePass_stdDH_sha512kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 0;
		ecdh_kdf_type = NID_sha512;
		break;
	case NID_dhSinglePass_cofactorDH_sha1kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha1;
		break;
	case NID_dhSinglePass_cofactorDH_sha224kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha224;
		break;
	case NID_dhSinglePass_cofactorDH_sha256kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha256;
		break;
	case NID_dhSinglePass_cofactorDH_sha384kdf_scheme:
		pkey_type = EVP_PKEY_EC;
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha384;
		break;
	case NID_dhSinglePass_cofactorDH_sha512kdf_scheme:
		ec_scheme = NID_secg_scheme;
		ecdh_cofactor_mode = 1;
		ecdh_kdf_type = NID_sha512;
		break;
	case NID_dhKeyAgreement:
		pkey_type = EVP_PKEY_DH;
		break;
	default:
		return 0;
	}
	*ppkey_type = pkey_type;
	*pec_scheme = ec_scheme;
	*pecdh_cofactor_mode = ecdh_cofactor_mode;
	*pecdh_kdf_type = ecdh_kdf_type;
	*pecdh_kdf_md = ecdh_kdf_md;
	*pecdh_kdf_outlen = ecdh_kdf_outlen;
	*pecdh_kdf_ukm = ecdh_kdf_ukm;
	*pecdh_kdf_ukmlen = ecdh_kdf_ukmlen;
	return 1;
}
unsigned char *pk_encrypt(EVP_PKEY *pk, const char *alg, const unsigned char *in,
	size_t inlen, size_t *outlen, ENGINE *e) {
	unsigned char *ret = NULL;
	int pkey_id, ec_scheme, ec_encrypt_param;
	EVP_PKEY_CTX *ctx = NULL;
	unsigned char *buf = NULL;
	if (!get_pke_info(alg, &pkey_id, &ec_scheme, &ec_encrypt_param)) {
		return NULL;
	}
	if (pkey_id != EVP_PKEY_id(pk)) {
		return NULL;
	}
	if (!(ctx = EVP_PKEY_CTX_new(pk, e))) {
		return NULL;
	}
	if (EVP_PKEY_encrypt_init(ctx) <= 0) {
		goto end;
	}
	if (EVP_PKEY_id(pk) == EVP_PKEY_EC && EC_GROUP_get_curve_name(
		EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pk))) == NID_sm2p256v1) {
		if (EVP_PKEY_CTX_set_ec_scheme(ctx, ec_scheme) <= 0
			|| EVP_PKEY_CTX_set_ec_encrypt_param(ctx, ec_encrypt_param) <= 0) {
			goto end;
		}
	}
	if (EVP_PKEY_encrypt(ctx, NULL, outlen, in, inlen) <= 0) {
		goto end;
	}
	if (!(buf = (unsigned char*)OPENSSL_zalloc(*outlen))) {
		goto end;
	}
	if (EVP_PKEY_encrypt(ctx, buf, outlen, in, inlen) <= 0) {
		goto end;
	}
	ret = buf;
	buf = NULL;
end:
	EVP_PKEY_CTX_free(ctx);
	OPENSSL_free(buf);
	return ret;
}
unsigned char *sk_decrypt(EVP_PKEY *sk, const char *alg, const unsigned char *in,
	size_t inlen, size_t *outlen, ENGINE *e) {
	unsigned char *ret = NULL;
	int pkey_id, ec_scheme, ec_encrypt_param;
	EVP_PKEY_CTX *ctx = NULL;
	unsigned char *buf = NULL;
	if (!get_pke_info(alg, &pkey_id, &ec_scheme, &ec_encrypt_param)) {
		return NULL;
	}
	if (pkey_id != EVP_PKEY_id(sk)) {
		return NULL;
	}
	if (!(ctx = EVP_PKEY_CTX_new(sk, e))) {
		return NULL;
	}
	if (EVP_PKEY_decrypt_init(ctx) <= 0) {
		goto end;
	}
	if (EVP_PKEY_id(sk) == EVP_PKEY_EC && EC_GROUP_get_curve_name(
		EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(sk))) == NID_sm2p256v1) {
		if (EVP_PKEY_CTX_set_ec_scheme(ctx, ec_scheme) <= 0
			|| EVP_PKEY_CTX_set_ec_encrypt_param(ctx, ec_encrypt_param) <= 0) {
			goto end;
		}
	}
	if (EVP_PKEY_decrypt(ctx, NULL, outlen, in, inlen) <= 0) {
		goto end;
	}
	if (!(buf = (unsigned char*)OPENSSL_zalloc(*outlen))) {
		goto end;
	}
	if (EVP_PKEY_decrypt(ctx, buf, outlen, in, inlen) <= 0) {
		goto end;
	}
	ret = buf;
	buf = NULL;
end:
	EVP_PKEY_CTX_free(ctx);
	OPENSSL_free(buf);
	return ret;
}
unsigned char *sk_sign(EVP_PKEY *sk, const char *alg, const unsigned char *dgst,
	size_t dgstlen, size_t *siglen, ENGINE *e) {
	unsigned char *ret = NULL;
	EVP_PKEY_CTX *ctx = NULL;
	unsigned char *sig = NULL;
	if (!(ctx = EVP_PKEY_CTX_new(sk, e))) {
		return NULL;
	}
	if (EVP_PKEY_sign_init(ctx) <= 0) {
		goto end;
	}
	if (EVP_PKEY_id(sk) == EVP_PKEY_EC && EC_GROUP_get_curve_name(
		EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(sk))) == NID_sm2p256v1) {
		if (EVP_PKEY_CTX_set_ec_scheme(ctx, NID_sm_scheme) <= 0) {
			goto end;
		}
	}
	if (EVP_PKEY_size(sk) <= 0) {
		goto end;
	}
	if (!(sig = (unsigned char*)OPENSSL_zalloc(EVP_PKEY_size(sk)))) {
		goto end;
	}
	*siglen = EVP_PKEY_size(sk);
	if (EVP_PKEY_sign(ctx, sig, siglen, dgst, dgstlen) <= 0) {
		goto end;
	}
	ret = sig;
	sig = NULL;
end:
	EVP_PKEY_CTX_free(ctx);
	OPENSSL_free(sig);
	return ret;
}
int pk_verify(EVP_PKEY *pk, const char *alg, const unsigned char *dgst,
	size_t dgstlen, const unsigned char *sig, size_t siglen, ENGINE *e) {
	int ret = -1;
	EVP_PKEY_CTX *ctx = NULL;
	if (!(ctx = EVP_PKEY_CTX_new(pk, e))) {
		printf("%s %d: error\n", __FILE__, __LINE__);
		goto end;
	}
	if (!EVP_PKEY_verify_init(ctx)) {
		printf("%s %d: error\n", __FILE__, __LINE__);
		goto end;
	}
	if (EVP_PKEY_id(pk) == EVP_PKEY_EC && EC_GROUP_get_curve_name(
		EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pk))) == NID_sm2p256v1) {
		if (EVP_PKEY_CTX_set_ec_scheme(ctx, NID_sm_scheme) <= 0) {
			goto end;
		}
	}
	if ((ret = EVP_PKEY_verify(ctx, sig, siglen, dgst, dgstlen)) <= 0) {
		printf("ret = %d\n", ret);
		ERR_print_errors_fp(stderr);
		goto end;
	}
end:
	EVP_PKEY_CTX_free(ctx);
	return ret;
}
unsigned char *sk_derive(EVP_PKEY *sk, const char *alg, EVP_PKEY *peer,
	size_t *outlen, ENGINE *e) {
	return NULL;
}

void get_publickey_algorithm_names(vector<string>& names)
{
    string s[] = {
			"sm2sign",
			"ecdsa-with-Recommended",
			"ecdsa-with-SHA1",
			"ecdsa-with-SHA256",
			"ecdsa-with-SHA512",
		};

    for( string tmp : s)
    {
        names.push_back(tmp);
    }
}

void get_sign_algorithm_names(vector<string> names, const char* name)
{
    if ( strcmp("EC", name) == true)
    {
        string s[]={"sm2sign",
			"ecdsa-with-Recommended",
			"ecdsa-with-SHA1",
			"ecdsa-with-SHA256",
			"ecdsa-with-SHA512",};

        for( string tmp : s)
        {
            names.push_back(tmp);
        }
    }
    else if ( strcmp("RSA", name) == true )
    {
        string s[]={"sm2sign",
			"RSA-SHA1",
			"RSA-SHA256",
			"RSA-SHA512",};

        for( string tmp : s)
        {
            names.push_back(tmp);
        }
    }
    else if ( strcmp("DSA", name) == true )
    {
        names.push_back("DSA-SHA1");
    }
    else
    {
        throw "Invalid public key algorithm";
    }
}

void get_public_key_encryption_names( vector<string> names,const char* pkey)
{
    if ( strcmp("RSA", pkey) )
    {
        string s[]={"RSAES-OAEP",};

        for( string tmp : s)
        {
            names.push_back(tmp);
        }
    }
    else if( strcmp("EC", pkey) )
    {
        string s[]={
			"ecies-recommendedParameters",
			"ecies-specifiedParameters",
			"ecies-with-x9-63-sha1-xor-hmac",
			"ecies-with-x9-63-sha256-xor-hmac",
			"ecies-with-x9-63-sha512-xor-hmac",
			"ecies-with-x9-63-sha1-aes128-cbc-hmac",
			"ecies-with-x9-63-sha256-aes128-cbc-hmac",
			"ecies-with-x9-63-sha512-aes256-cbc-hmac",
			"ecies-with-x9-63-sha256-aes128-ctr-hmac",
			"ecies-with-x9-63-sha512-aes256-ctr-hmac",
			"ecies-with-x9-63-sha256-aes128-cbc-hmac-half",
			"ecies-with-x9-63-sha512-aes256-cbc-hmac-half",
			"ecies-with-x9-63-sha256-aes128-ctr-hmac-half",
			"ecies-with-x9-63-sha512-aes256-ctr-hmac-half",
			"ecies-with-x9-63-sha1-aes128-cbc-cmac",
			"ecies-with-x9-63-sha256-aes128-cbc-cmac",
			"ecies-with-x9-63-sha512-aes256-cbc-cmac",
			"ecies-with-x9-63-sha256-aes128-ctr-cmac",
			"ecies-with-x9-63-sha512-aes256-ctr-cmac",
			"sm2encrypt-with-sm3",
			"sm2encrypt-with-sha1",
			"sm2encrypt-with-sha256",
			"sm2encrypt-with-sha512",
		};

        for( string tmp : s)
        {
            names.push_back(tmp);
        }
    }
    else
    {
        throw "Invalid public key algorithm";
    }
    
}

void get_derive_key_algorithm_names(vector<string> names, const char* pkey)
{
    if( strcmp("EC", pkey) == true)
    {
        names.push_back("sm2exchange");
    }
    else if ( strcmp("DH", pkey) == true )
    {
        string s[]={
			"dhSinglePass-stdDH-sha1kdf-scheme",
			"dhSinglePass-stdDH-sha224kdf-scheme",
			"dhSinglePass-stdDH-sha256kdf-scheme",
			"dhSinglePass-stdDH-sha384kdf-scheme",
			"dhSinglePass-stdDH-sha512kdf-scheme",
			"dhSinglePass-cofactorDH-sha1kdf-scheme",
			"dhSinglePass-cofactorDH-sha224kdf-scheme",
			"dhSinglePass-cofactorDH-sha256kdf-scheme",
			"dhSinglePass-cofactorDH-sha384kdf-scheme",
			"dhSinglePass-cofactorDH-sha512kdf-scheme",
			"dhKeyAgreement",
		};

        for( string tmp : s)
        {
            names.push_back(tmp);
        }
    }
}

EVP_PKEY* generate_private_key(const char* alg, vector<pair<string,string>>& args)
{
    auto ctx = new_pkey_keygen_ctx(alg, nullptr);
    if ( ctx == nullptr)
    {
        throw get_errors();
    }
    EVP_PKEY *pkey = NULL;

    if ( strcmp( "DH", alg) == true || strcmp("DSA", alg) == true)
    {
        if ( 1!= EVP_PKEY_paramgen_init(ctx) )
        {
            throw get_errors();
        }

        for( int i =0; i< args.size(); i++)
        {
            string name = args[i].first;
            string value = args[i].second;
            
            if( EVP_PKEY_CTX_ctrl_str(ctx, name.c_str(), value.c_str()) <= 0 )
            {
                throw get_errors();
            }
        }

        if ( 1!= EVP_PKEY_paramgen(ctx, &pkey))
        {
            throw get_errors();
        }
        if ( 1 != EVP_PKEY_keygen_init(ctx) )
        {
            throw get_errors();
        }
        if ( 1 != EVP_PKEY_keygen(ctx, &pkey) )
        {
            throw get_errors();
        }
        
    }
    else
    {
        if ( 1 != EVP_PKEY_keygen_init(ctx) )
        {
            throw get_errors();
        }

        for ( int i = 0; i< args.size(); i++ )
        {
            string name = args[i].first;
            string value = args[i].second;
            
            if( EVP_PKEY_CTX_ctrl_str(ctx, name.c_str(), value.c_str()) <= 0 )
            {
                throw get_errors();
            }
        }

        if ( 1 != EVP_PKEY_keygen(ctx, &pkey))
        {
            throw get_errors();
        }
    }
    
    EVP_PKEY_CTX_free(ctx);
    return pkey;
}

void free_pkey(EVP_PKEY* pkey)
{
    EVP_PKEY_free(pkey);
}

EVP_PKEY* new_private_key_from_pem(const char* pem, const char* pass)
{
    auto bio = BIO_new_mem_buf(pem, -1);
    if ( bio == NULL)
    {
        throw get_errors();
    }
    auto pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, &pass);
    if( pkey == nullptr)
    {
        throw get_errors();
    }
    BIO_free(bio);
    return pkey;
}

EVP_PKEY* new_public_key_from_pem(const char* pem)
{
    auto bio = BIO_new_mem_buf(pem, -1);
    if( bio == nullptr)
    {
        throw get_errors();
    }

    auto pkey = pem_read_bio_pubkey(bio);
    if ( pkey == nullptr)
    {
        throw get_errors();
    }

    BIO_free(bio);
    return pkey;
}

string get_pem(EVP_PKEY* pkey)
{
    auto bio = BIO_new(BIO_s_mem());
    if ( bio == nullptr)
    {
        throw get_errors();
    }
    if ( 1 != pem_write_bio_pubkey(bio, pkey))
    {
        throw get_errors();
    }
    char* tmp;
    int len = _BIO_get_mem_data(bio, &tmp);
    if( len <= 0)
    {
        throw get_errors;
    }

    string s(tmp,len); 

    BIO_free(bio);
    return s;
}

string get_pem(EVP_PKEY* pkey, const char* cipher, const char* pass)
{
    auto bio = BIO_new(BIO_s_mem());

    if ( bio == nullptr)
    {
        throw get_errors();
    }
    auto enc = EVP_get_cipherbyname(cipher);
    if ( enc == NULL)
    {
        throw get_errors();
    }

    /* FIXME: PKCS #5 can not use SM4 */
    if ( 1 != PEM_write_bio_PrivateKey(bio, pkey,
		EVP_des_ede3_cbc(), nullptr, int(0), nullptr, &pass))
    {
        throw get_errors();
    }
    
    char *tmp;
    int len = _BIO_get_mem_data(bio, &tmp);
    if( len <= 0)
    {
        throw get_errors;
    }

    string s(tmp,len); 
    
    BIO_free(bio);
    return s;
}

string get_public_key_pem(EVP_PKEY* pkey)
{
    auto bio = BIO_new(BIO_s_mem());
    if ( bio == nullptr)
    {
        throw get_errors();
    }
    if ( 1 != pem_write_bio_pubkey(bio, pkey) )
    {
        throw get_errors();
    }
    char* p;
    int len = _BIO_get_mem_data(bio, &p);
    if( len <= 0)
    {
        throw get_errors();
    }
    string s(p, len);

    BIO_free(bio);
    return s;
}

string get_text(EVP_PKEY* pkey)
{
    auto bio = BIO_new(BIO_s_mem());
    if ( bio == nullptr)
    {
        throw get_errors();
    }
    if ( 1 != EVP_PKEY_print_public(bio, pkey, 4, nullptr) )
    {
        throw get_errors();
    }
    char* p;
    int len = _BIO_get_mem_data(bio, &p);
    if( len <= 0)
    {
        throw get_errors();
    }
    string s(p, len);

    BIO_free(bio);
    return s;
}

void encrypt(EVP_PKEY* pkey, const char* alg, unsigned char* in, ENGINE* eng, unsigned char* out, size_t& outlen)
{
    unsigned char* tmp = pk_encrypt(pkey, alg, in, strlen((const char*)in), &outlen, nullptr);

    if ( tmp == nullptr)
    {
        throw get_errors();
    }
	memcpy(out, tmp, outlen);	
	free(tmp);
}

void decrypt(EVP_PKEY* pkey, const char* alg, unsigned char* in, int inlen,ENGINE* eng, unsigned char* out, size_t& outlen)
{
    unsigned char* tmp = sk_decrypt(pkey, alg, in,
		inlen, &outlen, nullptr);
    
    if( out == nullptr)
    {
        throw get_errors();
    }
	memcpy(out, tmp, outlen);	
	free(tmp);
}

void sign(EVP_PKEY* pkey, const char* alg, unsigned char* in, ENGINE* eng, unsigned char* out, size_t& outlen)
{
    unsigned char* tmp = sk_sign(pkey, alg, in,
		strlen((const char*)in), &outlen, nullptr);
    
    if( out == nullptr)
    {
        ERR_print_errors_fp(stderr);
        throw get_errors();
    }
	memcpy(out, tmp, outlen);	
	free(tmp);
}

bool verify(EVP_PKEY* pkey, const char* alg, unsigned char* dgst, unsigned char* sig, int siglen ,ENGINE* eng)
{
    if ( 1 != pk_verify(pkey, alg, dgst, strlen((const char*)dgst),
		sig, siglen, nullptr) )
    {
		ERR_print_errors_fp(stderr);
        return false;
    }

    return true;
}

void compute_SM2_IDDigest(EVP_PKEY* pkey, const char* id, unsigned char* out, size_t outlen)
{
    if ( EVP_PKEY_EC != EVP_PKEY_id(pkey) )
    {
        throw "Invalid public key type";
    }

    if ( 1 != SM2_compute_id_digest(EVP_sm3(), id, strlen((const char*)id), out, &outlen, EVP_PKEY_get0_EC_KEY(pkey)) )
    {
        throw get_errors();
    }
    out[outlen] = 0;
}








